package main

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/tarm/serial"
	//"encoding/json"
	"strconv"
	"strings"
	"time"
)

/*
串口相关操作
*/
//var serialObj serialport = serialport{}
var serialObjList []serialport = []serialport{}

type serialport struct {
	Port     string
	IsOpen   bool
	S        *serial.Port
	ClientID string
}

type serialBackData struct {
	port string
	//d  	[]byte
	data    string
	command string
}

func getSerialList(w http.ResponseWriter, ostype int) {
	cc := "se.list"
	list := SerialListGet()
	st := ""
	ii := ""
	for _, item := range list {
		ii = "{ \"p\":\"" + item.Port + "\", \"s\":" + strconv.Itoa(item.State) + "}"
		if st == "" {
			st = ii
		} else {
			st = st + "," + ii
		}
	}
	st = "[" + st + "]"
	messageSendBackClient(Message{D: st, C: cc}, w, ostype)
	//fmt.Printf("getSerialList: %s\n", db)
}

func serialClose(port string, w http.ResponseWriter, ostype int) bool {
	f, index := serialFindPortByName(port)
	if f {
		//fmt.Printf("delect before len: %s \n", len(serialObjList))
		serialObjList[index].S.Close()
		serialObjList[index].IsOpen = false
		serialObjList = append(serialObjList[:index], serialObjList[index+1:]...)
		//fmt.Printf("delect after len: %i \n ", len(serialObjList))
		return true
	} else {
		return false
	}
}

func serialOpen(port string, baud int, clientID string, w http.ResponseWriter, ostype int) {
	ff := serialClose(port, w, ostype)
	if ff {
		time.Sleep(2100 * time.Millisecond)
	}
	//c := &serial.Config{Name: "COM45", Baud: 115200}
	c := &serial.Config{Name: port, Baud: baud}
	s, err := serial.OpenPort(c)
	if err != nil {
		fmt.Printf(" %v \n", err)
		return
	}
	oj := serialport{}
	oj.S = s
	oj.IsOpen = true
	oj.Port = port
	oj.ClientID = clientID
	serialObjList = append(serialObjList, oj)
	go serialRead(port, w, ostype)
	messageSendBackClient(Message{P: port, C: "se.open", D: "Open Port ok"}, w, ostype)
}

func serialWrite(port string, m []byte, w http.ResponseWriter, ostype int) {
	f, index := serialFindPortByName(port)
	if !f {
		return
	}
	_, err := serialObjList[index].S.Write(m)
	if err != nil {
		MyPrintf(" %v \n", err)
		messageSendBackClient(Message{P: "", C: "se.write", D: "write error"}, w, ostype)
	}
}

func serialRead(port string, w http.ResponseWriter, ostype int) {
	for {
		f, index := serialFindPortByName(port)
		if !f || !serialObjList[index].IsOpen {
			break
		}
		buf := make([]byte, 1000)
		n, err := serialObjList[index].S.Read(buf)
		if err != nil {
			fmt.Printf(" %v \n", err)
			break
		} else {
			//fmt.Printf("Robot back data: %v \n", buf[:n])
			f, index = serialFindPortByName(port)
			if !f || !serialObjList[index].IsOpen {
				break
			}
			messageSendBackClient(Message{P: serialObjList[index].Port, C: "se.back", D: hex.EncodeToString(buf[:n])}, w, ostype)
		}
	}
}

/*
执行 up hex 命令
*/
func serialUploadHex(comm string, port string, w http.ResponseWriter, ostype int) {
	flag := false
	if len(port) > 3 {
		flag = true
		serialClose(port, w, ostype)
		time.Sleep(100 * time.Millisecond)
	}
	rs := ExecShell(comm)
	messageSendBackClient(Message{P: "", C: "se.uphex", D: IntToString(rs)}, w, ostype)
	if flag {
		time.Sleep(100 * time.Millisecond)
		go serialClose(port, w, ostype)
	}
}

/*
取某一个端口对象
*/
func serialFindPortByName(portname string) (bool, int) {
	portnamel := strings.ToLower(portname)
	for index, item := range serialObjList {
		if strings.ToLower(item.Port) == portnamel {
			return true, index
		}
	}
	return false, 0
}

/*
生成命令
k1: cc := "$exe -C $config -v -p atmega2560 -cwiring -P $port -b 115200 -D -U flash:w:$hexk1.hex:i"
k2://cc := "$exe -C $config -v -p atmega328p -c arduino -P $port -b 115200 -D -U flash:w:$hexk2.hex:i"
c2 := creatUpHexCommer(cc, "/dev/tty.wchusbserial14230")
*/
func creatUpHexCommer(comm string, port string) string {
	dir := GetCurrentDirectory()
	arduino := ""
	arch, os := GetOsInfo()
	exe := ""
	cnf := ""
	hex := dir + "/res/up/"
	temphex := dir + "/res/build/temp.hex"
	if os == "darwin" {
		arduino = dir + "/res/arduino/tools_darwin/avrdude/"
		exe = arduino + "bin/avrdude"
		cnf = arduino + "etc/avrdude.conf"
	} else if os == "windows" {
		//windows
		//dir2 := strings.Replace(dir, "/", "\\", -1)
		arduino = dir + "/res/arduino/tools_windows/avrdude/"
		exe = arduino + "bin/avrdude"
		cnf = arduino + "etc/avrdude.conf"
		hex = dir + "/res/up/"
	} else {
		if arch == "amd64" {
			arduino = dir + "/res/arduino/tools_linux_64/avrdude/"
			exe = arduino + "bin/avrdude"
			cnf = arduino + "etc/avrdude.conf"
		} else {
			arduino = dir + "/res/arduino/tools_linux_32/avrdude/"
			exe = arduino + "bin/avrdude"
			cnf = arduino + "etc/avrdude.conf"
		}
	}
	st := strings.Replace(comm, "$exe", exe, -1)
	st = strings.Replace(st, "$port", port, -1)
	st = strings.Replace(st, "$config", cnf, -1)
	st = strings.Replace(st, "$temphex", temphex, -1)
	st = strings.Replace(st, "$hex", hex, -1)
	st = strings.Replace(st, "$dir", dir, -1)
	return st
}

/*
以本地的 hex为源：
更新hex方式1：外面传相关参数，再加本地的参数；
*/
func SerialUploadHexByLocal(comm string, port string, w http.ResponseWriter, ostype int) {
	cmm := creatUpHexCommer(comm, port)
	serialUploadHex(cmm, port, w, ostype)
}

/*
原码直接更新到固件，直接调用 Arduino IDE
k2: =/Applications/Arduino.app/Contents/MacOS/Arduino --board arduino:avr:uno --port $port --upload $file/uno/uno.ino
*/
func serialUploadByArduinoCommCreat(port string, comm string) string {
	dir := GetCurrentDirectory()
	inoFile := dir + "/res/sources/"
	st := ""
	st = strings.Replace(comm, "$port", port, -1)
	st = strings.Replace(st, "$file", inoFile, -1)
	st = strings.Replace(st, "$dir", dir, -1)
	return st
}

// func serialUploadInoShell(port string, comm string, isback bool, w http.ResponseWriter, ostype int) {
// 	flag := false
// 	if len(port) > 3 {
// 		flag = true
// 		serialClose(port, w, ostype)
// 		time.Sleep(100 * time.Millisecond)
// 	}
// 	rs := ExecShell(comm)
// 	if isback {
// 		if rs == 0 {
// 			rs = -404
// 		}
// 		messageSendBackClient(Message{P: "", C: "se.upino", D: IntToString(rs)}, w, ostype)
// 	}
// 	if flag {
// 		time.Sleep(1100 * time.Millisecond)
// 		go serialClose(port, w, ostype)
// 	}
// }

/*
传来 ino 文件，用本地的 arduron IDE 运行更新 ino
更新 ino文件，并生成hex,并更新固件
*/
func SerialUpInoByLocalIDE(port string, inoHexStr string, board string, comm string, w http.ResponseWriter, ostype int) {
	cc := "se.upino"
	buff, err := hex.DecodeString(inoHexStr)
	if err != nil {
		messageSendBackClient(Message{P: "", C: cc, D: "-401", O: "error"}, w, ostype)
		return
	}
	dir := GetCurrentDirectory()
	// up file
	fileSt := string(buff[:])
	fileN := dir + "/res/sources/" + board + "/" + board + ".ino"
	fw := fileUpWriteByString(fileN, fileSt)
	//MyPrintf("\n fileN %s \n", fileN)
	//MyPrintf("\n fileST \n %s \n", fileSt)
	if fw == 0 {
		messageSendBackClient(Message{P: "", C: cc, D: "-402", O: "error"}, w, ostype)
		return
	}
	// build
	com2 := serialUploadByArduinoCommCreat(port, comm)
	//serialUploadInoShell(port, com2, true, w, ostype)
	serialClose(port, w, ostype)
	time.Sleep(100 * time.Millisecond)
	rs := ExecShell(com2)
	if rs == 0 {
		rs = -404
	}
	messageSendBackClient(Message{P: "", C: "se.upino", D: IntToString(rs), O: "ok"}, w, ostype)
	serialClose(port, w, ostype)
}

/*
云服务，ino 转为 hex
*/
func serialInoCreatHex(hexStr string, board string, w http.ResponseWriter, ostype int) {
	cc := "sv.hexback"
	buff, err := hex.DecodeString(hexStr)
	if err != nil {
		messageSendBackClient(Message{P: "", C: cc, D: "-401"}, w, ostype)
		return
	}
	path := GetCurrentDirectory()
	guid := GetGuid()
	p1 := path + "/res/sources/" + board
	p2 := path + "/res/build/" + guid //临时文件夹
	p3 := p2 + "/" + board            //源码
	p4 := p2 + "/b/"                  //生成的文件
	//creat path
	filePathCreat(p3)
	filePathCreat(p4)
	//copy
	fileCopyDir(p1, p3)
	// up file
	fileSt := string(buff[:])
	fileN := p3 + "/" + board + ".ino"
	fw := fileUpWriteByString(fileN, fileSt)
	//MyPrintf("\n fileN %s \n", fileN)
	//MyPrintf("\n fileST \n %s \n", fileSt)
	if fw == 0 {
		messageSendBackClient(Message{P: "", C: cc, D: "-402"}, w, ostype)
		deleteFileOnDisk(p2)
		return
	}
	exepath := SY.IDE //"/Applications/Arduino.app/Contents/MacOS/Arduino"
	// build
	com2 := serialCreathexCommCreat(exepath, p4, fileN, board)
	rs := ExecShell(com2)
	if rs == 0 {
		deleteFileOnDisk(p2)
		rs = -404
	}
	hexPath := p4 + "/" + board + ".ino.hex"
	//MyPrintf("\n %s \n \n", hexPath)
	hexStrBack := fileRead2Hex(hexPath)
	//MyPrintf(hexStrBack)
	//MyPrintf("\n \n")
	messageSendBackClient(Message{P: "", D: IntToString(rs), C: cc, O: hexStrBack}, w, ostype)
	if rs == 1 {
		time.Sleep(10 * time.Millisecond)
		deleteFileOnDisk(p2)
	}
}

func serialCreathexCommCreat(exepath string, buildpath string, sourefilepath string, board string) string {
	bor := ""
	if board == "mega" {
		bor = "arduino:avr:mega:cpu=atmega2560"
	} else {
		bor = "arduino:avr:uno"
	}
	com := exepath + " --board " + bor + " --pref build.path=" + buildpath + "  --verify " + sourefilepath
	return com
}

/*
从网上传来最新的hex,
更新本地的hex,然后 更新固件
*/
func serialYunHexUp(port string, hexStr string, commOld string, w http.ResponseWriter, ostype int) {
	cc := "se.yunhexup"
	buff, err := hex.DecodeString(hexStr)
	if err != nil {
		messageSendBackClient(Message{P: "", C: cc, D: "-401"}, w, ostype)
		return
	}
	path := GetCurrentDirectory()
	// up file
	fileSt := string(buff[:])
	fileN := path + "/res/build/temp.hex"
	fw := fileUpWriteByString(fileN, fileSt)
	if fw == 0 {
		messageSendBackClient(Message{P: "", C: cc, D: "-402"}, w, ostype)
		return
	}
	comNew := creatUpHexCommer(commOld, port)

	serialClose(port, w, ostype)
	time.Sleep(100 * time.Millisecond)
	rs := ExecShell(comNew)
	messageSendBackClient(Message{P: "", C: cc, D: IntToString(rs)}, w, ostype)
	time.Sleep(100 * time.Millisecond)
	go serialClose(port, w, ostype)
}

/*
传来 ino 文件，用本地的 arduron IDE 运行更新 ino
更新 ino文件，并生成hex,并更新固件
*/
func SerialUpInoByYun(port string, inoHexStr string, board string, commOld string, w http.ResponseWriter, ostype int) {
	cc := "se.yuninoup"
	//get hex form yun
	ss := httpPost(SY.InoServer, "c=sv.hexback&o="+board+"&d="+inoHexStr)
	MyPrintf("\n yun back hex:\n %s \n", ss)
	var j ClientJSONData
	err := json.Unmarshal([]byte(ss), &j)
	if err != nil {
		messageSendBackClient(Message{P: "", C: cc, D: "-411", O: "error"}, w, ostype)
		return
	}
	if j.D != "1" {
		messageSendBackClient(Message{P: "", C: cc, D: "-412", O: "error"}, w, ostype)
		return
	}
	hexStr := j.O
	//do by hex
	buff, err := hex.DecodeString(hexStr)
	if err != nil {
		messageSendBackClient(Message{P: "", C: cc, D: "-401", O: "error"}, w, ostype)
		return
	}
	path := GetCurrentDirectory()
	// up file
	fileSt := string(buff[:])
	fileN := path + "/res/build/temp.hex"
	fw := fileUpWriteByString(fileN, fileSt)
	if fw == 0 {
		messageSendBackClient(Message{P: "", C: cc, D: "-402", O: "error"}, w, ostype)
		return
	}
	comNew := creatUpHexCommer(commOld, port)

	serialClose(port, w, ostype)
	time.Sleep(100 * time.Millisecond)
	rs := ExecShell(comNew)
	messageSendBackClient(Message{P: "", C: cc, D: IntToString(rs), O: "ok"}, w, ostype)
	time.Sleep(100 * time.Millisecond)
	go serialClose(port, w, ostype)
}
